/* Arquivo fonte retirado do projeto SIGA-RENAPI sob Licença Pública Geral GNU,
 * adapatado para satisfazer as necessidades arquiteturais desta aplicação.
 */
package br.edu.ifrn.generic;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;

import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;

import br.edu.ifrn.annotation.OrdenacaoPadrao;
import br.edu.ifrn.annotation.manager.GerenciadorOrdenacaoPadrao;
import br.edu.ifrn.exception.NegocioException;
import br.edu.ifrn.utils.StringUtils;


public abstract class GenericDAO<Entidade> {

	private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
	
	protected EntityManager em;
	
	private static String ordenacaoPadrao, entidade, aliasEntidade;	
	
	/**
	 * armazena a classe (class) da entidade <T>
	 */
	private Class<Entidade> clazz;

	@SuppressWarnings("unchecked")
	public GenericDAO() {
		EntityManagerFactoryImpl factory = (EntityManagerFactoryImpl)Persistence.createEntityManagerFactory("cicc-persistence");
        this.em = factory.createEntityManager();
        
		// obtem o tipo da classe de <T> em tempo de execução
		ParameterizedType parameterizedType = (ParameterizedType) getClass()
			.getGenericSuperclass();
		this.clazz = (Class<Entidade>) parameterizedType
			.getActualTypeArguments()[0];
		 // define o nome da entidade
		entidade = clazz.getSimpleName();
		// define o alias da entidade
		aliasEntidade = entidade.substring(0, 1).toLowerCase()
				+ entidade.substring(1);

		// define a ordenacao padrao verificando
		//se a entidade tem a anotacao "@OrdenacaoPadrao"
			if(this.clazz.isAnnotationPresent(OrdenacaoPadrao.class)){
				String ordem = GerenciadorOrdenacaoPadrao.getOrdenacaoFormatada(this.clazz);
				ordenacaoPadrao = "ORDER BY " + ordem;
			}else{
				ordenacaoPadrao = null;
			}
		logger.info(">>> ordenacaoPadrao: " + ordenacaoPadrao);
	}

	public void setEntityManager(EntityManager em) {
		this.em = em;
	}

	public void inserir(final Entidade entidade) throws NegocioException {
		this.validaInstancia(entidade);
		try {
			// inclui no BD
			em.getTransaction().begin();
				this.beforeInsert(entidade);
				em.persist(entidade);
				this.afterInsert(entidade);
				//em.flush();
			em.getTransaction().commit();

		} catch (Exception e) {
			e.printStackTrace();
			throw new NegocioException(e);
		}
	}

	protected void beforeInsert(Entidade entidade) throws Exception {
	}
	protected void afterInsert(Entidade entidade) throws Exception {
	}
	protected void beforeUpdate(Entidade entidade) throws Exception {
	}
	protected void afterUpdate(Entidade entidade) throws Exception {
	}
	public Entidade alterar(final Entidade entidade) throws NegocioException {
		this.validaInstancia(entidade);
		Entidade retorno;
		try {
			
			// atualiza no BD
			em.getTransaction().begin();
				this.beforeUpdate(entidade);
				retorno = em.merge(entidade);
				this.afterUpdate(entidade);
				//em.flush();
			em.getTransaction().commit();
		} catch (Exception e) {
			throw new NegocioException(e);
		}
		return retorno;
	}

	public void remover(final Serializable id) throws NegocioException {
		Entidade entidade = this.consultarPorId(id);
		this.remover(entidade);
	}

	public void remover(final Entidade entidade) throws NegocioException {
		this.validaInstancia(entidade);
		try {
			// exclui do BD
			em.getTransaction().begin();
				em.remove(entidade);
				//em.flush();
			em.getTransaction().commit();

		} catch (Exception e) {
			throw new NegocioException(e);
		}
	}

	public Entidade consultarPorId(final Serializable id)
			throws NegocioException {
		try {
			// consulta o objeto pelo seu ID
				Entidade entidade = em.find(clazz, id);
				if(entidade == null)
					new Exception("Registro não encontrado no banco de dados");
			return entidade;
		} catch (Exception e) {
			throw new NegocioException(e);
		}
	}

	public List<Entidade> consultarTodos() throws NegocioException {
		return consultarTodos( ordenacaoPadrao );
	}

	public List<Entidade> consultarTodos(final String orderBy)
			throws NegocioException {
		return consultarTodos(null, orderBy);
	}

	public List<Entidade> consultarTodos(final String where,
			final String orderBy) throws NegocioException {
		try {
			// define a query
			String query = " SELECT " + aliasEntidade + " FROM " + entidade
					+ " " + aliasEntidade +" " + StringUtils.checkNull(where) + " "
					+ StringUtils.checkNull(orderBy);

			// executa a query
			return consultarTodosViaQuery(query, 0);

		} catch (Exception e) {
			throw new NegocioException(e);
		}

	}

	public Entidade consultar(final String where) throws NegocioException {
		try {
			if(StringUtils.isEmpty(where)){
				throw new NegocioException("[GenericDAO.executarQuery]: parametro 'where' é obrigatório");
			}
			// define a query
			String query = " SELECT " + aliasEntidade + " FROM " + entidade
					+ " " + aliasEntidade +" " + where;

			// executa a query
			return consultarViaQuery(query);

		} catch (Exception e) {
			throw new NegocioException(e);
		}
	}
	
	@SuppressWarnings("unchecked")
	public Entidade consultarViaQuery(final String query)
			throws NegocioException {

		try {
			// verifica se a query foi informada
			if (StringUtils.isEmpty(query)) {
				throw new NegocioException(
						"[GenericDAO.executarQuery]: parametro 'query' é obrigatório");
			}

			// variavel auxiliar que será o retorno deste método
			Entidade retorno = null;
			try {
				// executa a query
				Query q = em.createQuery(query);
				
				
				retorno = (Entidade) q.getSingleResult();
			} catch (NoResultException e) {
				return null;
			} catch (Exception e) {
				e.printStackTrace();
				throw new NegocioException("Erro ao executar a query: ["
						+ query + "]" + e.getMessage());
			}

			return retorno;

		} catch (Exception e) {
			throw new NegocioException(e);
		}

	}
	
	@SuppressWarnings("unchecked")
	public List<Entidade> consultarTodosViaQuery(final String query, int limit)
			throws NegocioException {

		try {
			// verifica se a query foi informada
			if (StringUtils.isEmpty(query)) {
				throw new NegocioException(
						"[GenericDAO.executarQuery]: parametro 'query' é obrigatório");
			}

			// variavel auxiliar que será o retorno deste método
			List<Entidade> retorno = null;
			try {
				// executa a query
				Query q = em.createQuery(query);
				if(limit != 0){
					q.setMaxResults(limit);
				}
				retorno = q.getResultList();
			} catch (Exception e) {
				e.printStackTrace();
				throw new NegocioException("Erro ao executar a query: ["
						+ query + "]" + e.getMessage());
			}

			return retorno;

		} catch (Exception e) {
			throw new NegocioException(e);
		}

	}
	
	public void validaInstancia(final Entidade entidade) throws NegocioException {
		if (entidade == null) {
			throw new NegocioException("Parâmetro 'entidade' não pode ser NULL");
		}
	}

}
